iT邦幫忙

2026 iThome 鐵人賽

DAY 5
0
Modern Web

重寫一套比我還老的系統:21 歲的校園文字廣播系統系列 第 5 篇

Day 5|今天的小黃鴨有點高級,會問會答,還不用消耗 token

  • 分享至 

  • xImage
  •  

書接上回,我們從原先的那份 DBML 討論了我們的資料庫設計。
在正式開始今天的內容之前,我用了一些神奇的人工智慧與工人智慧,產出了這份 DBML。

https://ithelp.ithome.com.tw/upload/images/20260919/20182031wLnGx7T2Ta.png

我去老哥偷捲,都自己偷偷動工不揪。

666 稀有鳥類馬德都部鳩出現了,一直部鳩部鳩的叫。
而且這不就要給你派工作了嗎?

所以我要幹啥?

幫我看一下這份 Schema。

啊你不是都畫完了?

對啊。

那我看什麼?

哪裡看不懂就問哪裡,有覺得奇怪的也問。

啊?傳統技藝小黃鴨 Debug 法又要重出江湖了嗎?

可惡被發現了。

今天的小黃鴨會說話

所謂「小黃鴨 Debug 法」,簡單來說就是找一隻小黃鴨,把自己的程式或想法從頭到尾講給它聽。

講著講著,有時候甚至不用等鴨子回答(雖然你應該是等不到它回答啦),你就會突然發現:

「欸幹,我這邊是不是寫錯了?」

至於我們今天的小黃鴨不只會聽,還會問,甚至還會吐槽。

比一般的小黃鴨好用很多,還不用給 API 充錢,就是需要吃點巴拿拿(Banana)犒勞一下。

所以我現在是鴨還是猴子?

你是猿神可以了嗎,趕緊開始。

行。

欸老哥,這裡的設計有點猛啊。

https://ithelp.ithome.com.tw/upload/images/20260919/20182031ZTOlXkcOvO.png

這裡的 permissions 是準備要用昨天提到的位運算對吧。

不錯啊,看一眼就懂了,一般人應該會先問為什麼把 permissions 設定成整數。
既然你知道我要幹什麼了,那就由你來跟大家解釋這邊的設計吧。

說好的小黃鴨呢?不應該是你解釋給我聽嗎?

能者過...…咳咳,能者多勞嘛,交給你了。

為什麼 Permission 要設定成 BIGINT?

行,我們來看一下這段:

Table positions {
  id uuid [pk]
  name varchar(255) [not null, unique]
  permissions bigint [not null, default: 0]
  description text
}

以正常的思路來說,我們應該會開一個 Permission 表,然後開一個中介表來記錄哪個 position 有哪些權限對吧。

但蘇某沒有打算這麼做。

假設我們現在有四個權限要記錄:

  • 查看廣播
  • 發布廣播
  • 回覆廣播
  • 處理報修

我們可以把它們塞在同一個整數的不同位元:

查看廣播    0001
發布廣播    0010
回覆廣播    0100
處理報修    1000

這樣如果這個人所擁有的權限是:查看廣播、發布廣播。

我們只需要對 0001 跟 0010 做一次 OR 運算得到 0011 也就是 3。

之後要檢查某個 Permission,也只需要透過 AND 判斷對應的 bit 有沒有被打開。

位運算在電腦裡本身是非常便宜的,而且這樣一來,一整組 Permission 就可以直接壓在一個 BIGINT 裡,不需要為每一項 Permission 都建立一筆關聯資料。

我補充一下,位運算本身很便宜,不過老實說,我們學校這點使用量根本輪不到 CPU 操心。
真正方便的是,一整組 Permission 可以直接用一個值表示,之後 Role、Position 甚至 Override 都可以用同一套方式做 OR、AND。

缺點也很明顯,人沒辦法從 3 這個魔法數字直接知道這個人有 查看廣播、發布廣播 這兩個權限。

不錯,說得挺好,那你覺得這個可讀性的問題怎麼解決?

開常數定義不就行了。

READ_MESSAGE = 1 << 0
SEND_MESSAGE = 1 << 1
REPLY_MESSAGE = 1 << 2
HANDLE_REPAIR = 1 << 3

對,所以真正寫 Code 的時候,我們不需要直接操作 1、2、4 這些神奇數字。
簡單來說,到這裡我們只需要記住一件事:
一個整數,就可以直接表示一整組 Permission。

好,中場休息,喝口水。

我也。欸,剛剛幫你講課,等等下課後珍奶一杯啊。

知道了,我放冰箱,一年後自己來拿。

吔屎丫你!放一年的珍奶是能喝?

等下,老兄我這裡有個問題。

https://ithelp.ithome.com.tw/upload/images/20260919/20182031Ku8Uby6PZw.png

嘿?說。

你這邊的 account 我知道為了共用帳號區分密碼所以設計成 not unique
但這樣要怎麼防止老師的帳號跟學生衝撞到?

利用 position_id 跟 account 組聯合唯一鍵啊。

那這樣你之後訊息你要怎麼設計推播範圍?如果有老師跟學生的 account 撞名怎麼辦?

Shift。

……等等。

不對!我想到了,我們其實根本不需要拿 account 來決定廣播要送給誰。

啊?不是說要發給班級嗎,不拿帳號怎麼知道哪個是哪班?

Account 真的是「班級」嗎?

就是因為要發給班級,所以才不應該看 Account。
account 是拿來登入的。
我們可以在 group 上新增一個欄位叫 type,用這個欄位去描述這個 group 的屬性。
例如:class、grade、office、root……
在搜尋時,filter 只要設定 type = class,就能快速找出能設定成目標的 group 了。
再利用共同父節點,對可廣播的組織結構進行還原。

所以你前面硬是多拆一張 groups 出來的目的就是在此?

想多了,我本來只是想要做授權用的,沒想到在這裡派上新的用場了。

欸欸那你那個 parent_id 是不是可以搞成這樣?

高中部
├── 高一
│   ├── 101
│   ├── 102
│   └── 103
├── 高二
│   ├── 201
│   ├── 202
│   └── 203
└── 高三

正確。

在目前的設計中,真正能成為發送目標的只有 type = class 的 Group。

中間的 Group 主要負責描述組織結構,以及讓 UI 可以一次選取一整個範圍。

等一下,那這樣有點香欸。

因為資料庫裡本來就是:

高二
├── 201
├── 202
└── 203

前端顯示出來也是:

高二
├── 201
├── 202
└── 203

你根本不用再另外維護一份「高二有哪些班」?

就說叫建模了,那必須建得像啊 ∠( ᐛ 」∠)_

聽起來很像是剛剛才發現可以這樣用,裝成自己早就想好了。

我不是說了我剛剛才發現可以這樣用嗎 ==。

不過這確實是我很喜歡這個設計的地方。

對使用者而言,它可能完全沒有任何改變。

但對系統而言,可維護性卻提升了:UI 所呈現的組織結構,和資料庫所理解的組織結構,變成了同一件事情。

而且這樣剛才那個 account 撞名的問題其實也消失了。
因為這樣系統根本不關心你的登入帳號叫什麼。
它在廣播推送中只關心:「你到底是哪個 Group?」

對。

看來今天這隻小黃鴨還真的有點用。

賽跑狀況?何意味?

我其實還有想到一個問題,你應該不會想到。

嘿?

你說,如果有兩個能修改組織描述的人,同時修改同一段組織結構,
會發生什麼事呢?

?

好刁鑽的問題,Race Condition?

對...…

我有兩個思路。

?這麼快?

  1. 想辦法讓整次修改變成一個不可分割的操作
  2. 直接上鎖

有料,但前者很難設計。

?為什麼?

做到那裡再跟你講,這個問題可以在後端解決。

吊我胃口。

對,就吊你們胃口。

不要臉。

(響過一陣敲鑼打鼓聲)

欲知後「逝」如何,且聽下回分解。


上一篇
Day 4|到底誰是 User?把學校的所有人丟進資料庫看看
下一篇
Day 6|終於可以開始寫 Code 了……嗎?
系列文
重寫一套比我還老的系統:21 歲的校園文字廣播系統 共 20 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言